package shark.flow.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import shark.flow.cmd.DeleteFlowableProcessInstanceCmd;
import shark.flow.enums.CommentTypeEnum;
import shark.flow.model.Query;
import shark.flow.service.IFlowBpmnModelService;
import shark.flow.service.IFlowProcessInstanceService;
import shark.flow.vo.ProcessInstanceQueryVo;
import shark.flow.vo.ProcessInstanceVo;
import shark.flow.vo.StartProcessInstanceVo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Slf4j
@Service
public class FlowProcessInstanceServiceImpl implements IFlowProcessInstanceService {

    @Autowired
    private ProcessEngine processEngine;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private IdentityService identityService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private ManagementService managementService;

    @Autowired
    private IFlowBpmnModelService bpmnModelService;

    @Transactional
    @Override
    public ProcessInstance startProcessInstanceByKey(StartProcessInstanceVo param) {
        if (StringUtils.isNotBlank(param.getProcessDefinitionKey()) &&
                StringUtils.isNotBlank(param.getBusinessKey())) {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionKey(param.getProcessDefinitionKey())
                    .latestVersion().singleResult();

            if (processDefinition.isSuspended() || processDefinition == null) {
                log.error("该{}流程已被挂起", param.getProcessDefinitionKey());
                return null;
            }
            String creator = param.getCreator();
            identityService.setAuthenticatedUserId(creator);
            ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
                    .processDefinitionKey(param.getProcessDefinitionKey().trim())
                    .name(param.getFormName())
                    .start();

            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
            if (task != null) {
                taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), CommentTypeEnum.TJ.getName(), "发起流程申请");
                taskService.setAssignee(task.getId(), creator);
                taskService.complete(task.getId());
            }
            return processInstance;
        }
        return null;
    }

    @Override
    public IPage<ProcessInstanceVo> getInstancePage(ProcessInstanceQueryVo params) {
        Assert.notNull(params.getQuery(), "查询流程实例，Query不能为空！");

        Query query = params.getQuery();
        ProcessInstanceQuery instanceQuery = runtimeService.createProcessInstanceQuery();
        if (params.getUserCode() != null) {
            instanceQuery.involvedUser(params.getUserCode());
        }
        List<ProcessInstance> instances = instanceQuery.listPage(query.getPageNum(), query.getPageSize());
        int total = instanceQuery.list().size();
        List<ProcessInstanceVo> instanceVos = new ArrayList<>(instances.size());
        for (ProcessInstance processInstance : instances) {
            ProcessInstanceVo vo = new ProcessInstanceVo(processInstance);
            instanceVos.add(vo);
        }
        IPage<ProcessInstanceVo> page = new Page<>();
        page.setRecords(instanceVos);
        page.setPages(query.getPageNum());
        page.setSize(query.getPageSize());
        page.setTotal(total);
        page.setCurrent(query.getPageNum());
        return page;
    }

    @Override
    public IPage<ProcessInstanceVo> getMyProcessInstances(ProcessInstanceQueryVo params, Query query) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        if (params.getUserCode() != null) {
            historicProcessInstanceQuery.startedBy(params.getUserCode());
        }
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.listPage(query.getPageNum(), query.getPageSize());
        int total = historicProcessInstanceQuery.listPage(query.getPageNum(), query.getPageSize()).size();
        IPage<ProcessInstanceVo> page = new Page<>();
        List<ProcessInstanceVo> instanceVos = new ArrayList<>();
        for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
            ProcessInstanceVo vo = new ProcessInstanceVo();
            vo.setProcessInstanceId(historicProcessInstance.getId());
            vo.setProcessDefinitionId(historicProcessInstance.getProcessDefinitionId());
            vo.setStartTime(historicProcessInstance.getStartTime());
            vo.setEndTime(historicProcessInstance.getEndTime());
            vo.setStarterId(historicProcessInstance.getStartUserId());
            instanceVos.add(vo);
        }
        page.setRecords(instanceVos);
        page.setPages(query.getPageNum());
        page.setSize(query.getPageSize());
        page.setTotal(total);
        page.setCurrent(query.getPageNum());
        return page;
    }

    @Override
    public byte[] createImage(String processInstanceId) throws IOException {
        String processDefinitionId;
        // 获取当前的流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        // 如果流程已经结束，则得到结束节点
        if (Objects.isNull(processInstance)) {
            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if (Objects.isNull(pi)) {
                throw new FlowableException("未查询到相关流程实例！");
            }
            processDefinitionId = pi.getProcessDefinitionId();
        } else {// 如果流程没有结束，则取当前活动节点
            // 根据流程实例ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }

        // 获得活动的节点
        List<HistoricActivityInstance> highLightedFlowList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();

        List<String> highLightedFlows = new ArrayList<>();
        List<String> highLightedNodes = new ArrayList<>();
        //高亮线
        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
            if (("userTask").equals(tempActivity.getActivityType())) {
                if (tempActivity.getEndTime() == null) {
                    highLightedNodes.add(tempActivity.getActivityId());
                }
            }
            /*if ("sequenceFlow".equals(tempActivity.getActivityType())) {
                //高亮线
                highLightedFlows.add(tempActivity.getActivityId());
            } else {
                //高亮节点
                highLightedNodes.add(tempActivity.getActivityId());
            }*/
        }

        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration();
        //获取自定义图片生成器
        ProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodes, highLightedFlows, configuration.getActivityFontName(),
                configuration.getLabelFontName(), configuration.getAnnotationFontName(), configuration.getClassLoader(), 1.0, true);

        byte [] bytes = this.toByteArray(in);

        return bytes;
    }

    @Override
    public void deleteProcessInstanceById(String processInstanceId) {
        long count = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).count();
        if (count > 0) {
            DeleteFlowableProcessInstanceCmd cmd = new DeleteFlowableProcessInstanceCmd(processInstanceId, "删除流程实例", true);
            managementService.executeCommand(cmd);
        } else {
            historyService.deleteHistoricProcessInstance(processInstanceId);
        }
    }

    @Override
    public void suspendOrActivateProcessInstanceById(String processInstanceId, Integer suspensionState) {
        if (suspensionState == 1) {
            runtimeService.suspendProcessInstanceById(processInstanceId);
        } else {
            runtimeService.activateProcessInstanceById(processInstanceId);
        }
    }

    @Override
    public void stopProcessInstanceById(String processInstanceId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance != null){
            List<EndEvent> endNodes = bpmnModelService.findEndFlowElement(processInstance.getProcessDefinitionId());
            String endId = endNodes.get(0).getId();
            List<Execution> executions = runtimeService.createExecutionQuery().parentId(processInstanceId).list();
            List<String> executionIds = new ArrayList<>();
            runtimeService.createChangeActivityStateBuilder()
                    .moveExecutionsToSingleActivityId(executionIds, endId)
                    .changeState();
        }
    }

    @Override
    public String revokeProcess(String processInstanceId) {
        return null;
    }

    private byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }
}
